/*
	Contains source for the Win32 window encapsulation class 'Window'

	Author: Aidan Goddard 9/3/14
*/

#include"Window.h"

/////////////////////////////////////////////////////////////
// static data
std::set<HWND> Snowstorm::Window::hwnd_list;
std::map<HWND, Snowstorm::Window*> Snowstorm::Window::hwnd_map;
Snowstorm::Window *Snowstorm::Window::swi;
Snowstorm::w_mode_t Snowstorm::Window::mode;

/////////////////////////////////////////////////////////////
// initialise the window subsystem
int Snowstorm::Window::Initialise(w_mode_t mode)
{
	// check the mode given is recognised)
	if(mode > 1)
	{
		return INIT_MODE_NOT_RECOGNISED;
	}

	// mode is recognised
	// construct necessary WinAPI window classes
	// get the process hinstance
	HINSTANCE hinstance = GetModuleHandleW(NULL);

	// first register main window class
	WNDCLASSEX wnd_class;
	wnd_class.cbSize = sizeof(WNDCLASSEX);
	wnd_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
	wnd_class.cbClsExtra = 0;
	wnd_class.cbWndExtra = 0;
	wnd_class.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wnd_class.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
	wnd_class.lpszMenuName  = NULL;
	wnd_class.lpszClassName = L"SNOWSTORM_WINDOW_CLASS";
	wnd_class.hInstance = hinstance;
	wnd_class.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

	// check if multi or single window mode
	if(mode == MODE_SINGLE)
	{
		wnd_class.lpfnWndProc = Window::SingleWindowRouter;
		wnd_class.hCursor = NULL;
	}
	else
	{
		wnd_class.lpfnWndProc = Window::MsgRouter;
		wnd_class.hCursor = LoadCursor(NULL, IDC_ARROW);
	}

	if(!RegisterClassEx(&wnd_class))
	{
		// return error
		return INIT_CLASS_REGISTRATION_FAIL;
	}

	// clear the HWND -> Window object map
	Window::hwnd_map.clear();
	Window::swi = NULL;
	Window::hwnd_list.clear();

	// set mode
	Window::mode = mode;

	// finished successfully
	return INIT_SUCCESS;
}


/////////////////////////////////////////////////////////////
// deinitialise the window subsystem and cleanup
void Snowstorm::Window::Finish()
{
	// ensure all windows are closed
	// call the public close function for each window
	if(Window::mode == MODE_MULTIPLE)
	{
		// get number of active windows
		int num_windows = (int)Window::hwnd_list.size();

		// close them
		std::set<HWND>::iterator it;
		for(it = Window::hwnd_list.begin(); it != Window::hwnd_list.end(); it++)
		{
			// get the HWND, then the window
			HWND h = *it;
			Window *wnd = Window::hwnd_map[h];

			// close it
			wnd->Kill();
		}

		// then empty the containers
		Window::hwnd_list.clear();
		Window::hwnd_map.clear();
	}
	else
	{
		// call the single window kill
		Window::swi->Kill();
	}
	
	// get the process hinstance
	HINSTANCE hinstance = GetModuleHandleW(NULL);

	// unregister the window class
	UnregisterClass(L"SNOWSTORM_WINDOW_CLASS", hinstance);

	// finished
	return;
}

/////////////////////////////////////////////////////////////
// window create function
Snowstorm::Window* Snowstorm::Window::Create(WindowAttributes *window_attributes)
{
	// check mode and state is correct
	if(Window::mode == MODE_SINGLE && Window::swi)
	{
		return NULL;
	}

	// get hinstance
	HINSTANCE hinstance = GetModuleHandleW(NULL);

	// get parameters
	DWORD style = 0;
	int x, y, width, height;
	x = window_attributes->x;
	y = window_attributes->y;
	width = window_attributes->width;
	height = window_attributes->height;
	HWND parent = window_attributes->parent_hwnd;

	if(window_attributes->window_type == TYPE_NO_BOARDER)
	{
		style = WS_OVERLAPPED | WS_POPUP;
	}
	else if(window_attributes->window_type == TYPE_POPUP)
	{
		style = WS_CAPTION | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_SIZEBOX;
		RECT rc = {0, 0, width, height};
		AdjustWindowRectEx(&rc, style, FALSE, NULL);
		width = rc.right - rc.left;
		height = rc.bottom - rc.top;
	}
	else if(window_attributes->window_type == TYPE_FULLSCREEN || window_attributes->window_type == TYPE_EXCLUSIVE_FULLSCREEN)
	{
		// fullscreen
		style = WS_OVERLAPPED | WS_POPUP;
		x = 0;
		y = 0;
		width = (int)GetSystemMetrics(SM_CXSCREEN);
		height = (int)GetSystemMetrics(SM_CYSCREEN);
	}
	else
	{
		// not a recognised type
		return NULL;
	}

	if(window_attributes->parent_hwnd)
	{
		style = style | WS_CHILD | WS_BORDER | WS_SYSMENU;
	}
	else if(window_attributes->window_type == TYPE_POPUP)
	{
		style = style | WS_POPUPWINDOW;
	}

	// attempt to create the win32 window
	HWND new_window = CreateWindowExW(WS_EX_APPWINDOW, L"SNOWSTORM_WINDOW_CLASS", window_attributes->title, style, x, y, width, height, parent, NULL, hinstance, NULL);
	if(!new_window)
	{
		return NULL;
	}

	// set a few more fullscreen things
	if(window_attributes->window_type == TYPE_FULLSCREEN)
	{
		SetWindowPos(new_window, HWND_TOPMOST, 0, 0, width, height, NULL);
	}

	// then create an associated object for it
	Window *wnd = new Window(new_window);
	wnd->parent = parent;
	wnd->style = style;

	// next register it
	if(Window::mode == MODE_SINGLE)
	{
		Window::swi = wnd;
	}
	else
	{
		Window::hwnd_list.emplace(new_window);
		Window::hwnd_map[new_window] = wnd;
	}

	if(window_attributes->catch_input)
	{
		// tell windows we want to listen for keyboard & mouse input
		RAWINPUTDEVICE devices[2];
		devices[0].usUsagePage = 0x01;				// Generic desktop page
		devices[0].usUsage = 0x06;					// keyboard
		devices[0].dwFlags = RIDEV_NOLEGACY;		// do not generate legacy messages such as WM_KEYDOWN
		devices[0].hwndTarget = new_window;
		devices[1].usUsagePage = 0x01;				// Generic desktop page
		devices[1].usUsage = 0x02;					// mouse
		devices[1].dwFlags = 0;
		devices[1].hwndTarget = new_window;
		BOOL ret = RegisterRawInputDevices(devices, 2, sizeof(RAWINPUTDEVICE));

		// check if device registration failed
		if(!ret)
			return NULL;

		// init the input controls
		wnd->key_pressed_event = NULL;
		wnd->key_released_event = NULL;
		wnd->mouse_event = NULL;
		wnd->mouse_abs_x = 0;
		wnd->mouse_abs_y = 0;
		wnd->KEYSTATES[0] = 0;
		wnd->KEYSTATES[1] = 0;
		wnd->KEYSTATES[2] = 0;
		wnd->KEYSTATES[3] = 0;
		wnd->KEYSTATES[4] = 0;
		wnd->KEYSTATES[5] = 0;
		wnd->KEYSTATES[6] = 0;
		wnd->KEYSTATES[7] = 0;
	}

	// finished, return it
	return wnd;
}


/////////////////////////////////////////////////////////////
// window remove function
void Snowstorm::Window::Remove(Window *wnd)
{
	// check which mode is in operation
	if(Window::mode == MODE_SINGLE)
	{
		Window::swi = NULL;
	}
	else
	{
		Window::hwnd_map.erase(wnd->hwnd);
		Window::hwnd_list.erase(wnd->hwnd);
	}
	return;
}



/////////////////////////////////////////////////////////////
// Window constructor
Snowstorm::Window::Window(HWND hwnd)
{
	this->hwnd = hwnd;
	return;
}


/////////////////////////////////////////////////////////////
// Window destructor
Snowstorm::Window::~Window()
{
}


/////////////////////////////////////////////////////////////
// Show window function
void Snowstorm::Window::Show()
{
	ShowWindow(this->hwnd, SW_SHOW);
	return;
}


/////////////////////////////////////////////////////////////
// Hide window function
void Snowstorm::Window::Hide()
{
	ShowWindow(this->hwnd, SW_HIDE);
	return;
}


/////////////////////////////////////////////////////////////
// Kill window function
void Snowstorm::Window::Kill()
{
	DestroyWindow(this->hwnd);
	Window::Remove(this);
	delete this;
	return;
}



/////////////////////////////////////////////////////////////
// HWND fetcher
HWND Snowstorm::Window::GetHWND()
{
	return this->hwnd;
}


/////////////////////////////////////////////////////////////
// resize window
void Snowstorm::Window::Resize(int x, int y, int width, int height)
{
	// resize the window
	SetWindowPos(this->hwnd, NULL, x, y, width, height, SWP_DRAWFRAME);
	return;
}

void Snowstorm::Window::Resize(int width, int height)
{
	// get the current position of the window relative to its parent
	RECT rect;
	GetWindowRect(this->hwnd, &rect);
	MapWindowPoints(HWND_DESKTOP, GetParent(this->hwnd), (LPPOINT)&rect, 2);

	// resize the window
	SetWindowPos(this->hwnd, NULL, rect.left, rect.top, width, height, SWP_DRAWFRAME);
	
	return;
}


/////////////////////////////////////////////////////////////
// mimimize/maximize
void Snowstorm::Window::Minimize()
{
	ShowWindow(this->hwnd, SW_SHOWMINIMIZED);
	return;
}

void Snowstorm::Window::Maximize()
{
	ShowWindow(this->hwnd, SW_SHOWMAXIMIZED);
	return;
}

/////////////////////////////////////////////////////////////
// get window style
DWORD Snowstorm::Window::GetWindowStyle()
{
	return this->style;
}